package com.beordie.version_2.aop.support;

import com.beordie.version_2.aop.aspect.BODAdvice;
import com.beordie.version_2.aop.config.BODAopConfig;
import com.beordie.version_2.aop.interceptor.BODAfterReturningAdviceInterceptor;
import com.beordie.version_2.aop.interceptor.BODBeforeAdviceInterceptor;
import com.beordie.version_2.aop.interceptor.BODThrowingAdviceInterceptor;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description
 * @Date 2022/2/19 9:11
 * @Created 30500
 */
public class BODAdviseSupport {
    /**
     * 保存方法和增强之间的关系
     */
    private Map<Method, List<Object>> methodCache;
    /**
     * 类型正则匹配
     */
    private Pattern pointCutClassPattern;
    /**
     * 目标对象
     */
    private Object targetInstance;
    /**
     * 目标类
     */
    private Class targetClass;
    /**
     * 切面规则
     */
    private BODAopConfig config;

    public BODAdviseSupport(BODAopConfig config) {
        this.config = config;
    }

    public Object getTargetInstance() {
        return targetInstance;
    }

    public void setTargetInstance(Object targetInstance) {
        this.targetInstance = targetInstance;
    }

    private void parse() {
        String pointCutRegex = this.config.getPointCut()
                .replaceAll("\\.", "\\\\.")
                .replaceAll("\\\\.\\*", ".*")
                .replaceAll("\\(", "\\\\(")
                .replaceAll("\\)", "\\\\)");
        String pointCutForClassRegex = pointCutRegex.substring(0, pointCutRegex.lastIndexOf("\\(") - 4);
        this.pointCutClassPattern = Pattern.compile("class " +
                pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
        this.methodCache = new HashMap<>();
        Pattern pointCutPattern = Pattern.compile(pointCutRegex);
        try {
            Map<String, Method> aspectMethods = new HashMap<>();
            Class<?> clazz = Class.forName(this.config.getAspectClass());
            //  迭代切面对象进行方法缓存
            for (Method method : clazz.getMethods()) {
                aspectMethods.put(method.getName(), method);
            }
            //  匹配目标对象的方法规则
            for (Method method : this.targetClass.getMethods()) {
                String methodString = method.toString();
                //  去除异常抛出的影响
                if (methodString.contains("throws")) {
                    methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
                }
                Matcher matcher = pointCutPattern.matcher(methodString);
                if (matcher.matches()) {
                    //  TMD 注意是切面对象目标
                    List<Object> advices = new LinkedList<>();
                    if (!(this.config.getAspectBefore() == null || "".equals(this.config.getAspectBefore().trim()))) {
                        advices.add(
                                new BODBeforeAdviceInterceptor(
                                        clazz.newInstance(), aspectMethods.get(this.config.getAspectBefore())));
                    }
                    if (!(this.config.getAspectAfter() == null || "".equals(this.config.getAspectAfter().trim()))) {
                        advices.add(
                                new BODAfterReturningAdviceInterceptor(
                                        clazz.newInstance(), aspectMethods.get(this.config.getAspectAfter())));
                    }
                    if (!(this.config.getAspectAfterThrow() == null || "".equals(this.config.getAspectAfterThrow().trim()))) {
                        BODThrowingAdviceInterceptor advice =
                                new BODThrowingAdviceInterceptor(clazz.newInstance(), aspectMethods.get(this.config.getAspectAfterThrow()));
                        advice.setThrowName(this.config.getAspectAfterThrowingName());
                        advices.add(advice);
                    }
                    this.methodCache.put(method, advices);
                }
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }

    }

    public Class getTargetClass() {
        return targetClass;
    }

    public void setTargetClass(Class targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    public boolean pointCutMatch() {
        return this.pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }


    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) throws NoSuchMethodException {
        List<Object> cache = this.methodCache.get(method);
        if (cache == null) {
            Method classMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());
            cache = this.methodCache.get(classMethod);
            this.methodCache.put(classMethod, cache);
        }
        return cache;
    }
}
